Feat/local model providers - #1
Merged
Merged
Conversation
Both recipes declared embeddings only, so a local install could index and search a brain but never reason over it — every synthesis step routed to a hosted provider because no local recipe offered a chat touchpoint. Both now declare chat and expansion with tool calling, which is what lets the subagent loop run locally. Model lists are advisory (openai-compat tier never rejects an unlisted id), so any model the user pulled or launched resolves. Costs are pinned to 0 — a nonzero rate here would corrupt every --max-usd pre-flight for a local brain. Also adds a reachability probe to the ollama recipe (llama-server already had one) and threads baseURL through probeOllama, so a config-only URL override is visible to the probe instead of silently checking localhost. A stopped daemon is the local failure mode no key check can catch. Drops ollama from the "embedding-only, use a hosted provider for chat" hint in assertTouchpoint; the remaining voyage case now points at the local lane first, since a user who hits that wall is usually trying to avoid a hosted key rather than shopping for one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four openai-compatible providers, closing the gap against the provider set carried by sibling projects. All are chat + expansion; fireworks also serves embeddings (nomic-embed-text-v1.5, Matryoshka 64..768), making it the only one of the four that can run a whole brain alone. Endpoints, auth env vars and capability flags were verified against each provider's live documentation rather than recalled — several model ids and context limits had moved. supports_structured_outputs is set only where the provider documents a strict json_schema response_format (cerebras, fireworks); the rest stay on the schemaless expansion path, where a wrong false costs one retry and a wrong true breaks expand(). Cost is left undefined for cerebras, fireworks and sambanova: none publishes a stable public per-token table, and a fabricated rate corrupts a --max-usd budget gate silently, which is worse than an absent one. xai publishes rates and carries its sub-200k-prompt tier, with the 2x above-200k tier noted in the recipe. Excluded deliberately: cerebras zai-glm-4.7 (scheduled for deprecation nine days out) and sambanova's preview tier, so the wizard cannot default onto a model whose availability moves without notice. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…solves Step 7 of the model-resolution chain returned a hardcoded anthropic:* id. For a brain with an Anthropic credential that is correct. For a brain deliberately run without one it is not a default at all — it names a model the install cannot reach, and the resulting NO_ANTHROPIC_API_KEY reads as "gbrain requires Anthropic" rather than "nothing told me what to use". Users hit this after correctly pointing chat_model at a local provider, because the gateway's chat_model and this tier resolver were two independent notions of "the default model" and only one was consulted here. resolveTierDefault() now returns TIER_DEFAULTS whenever hasAnthropicKey() resolves — keyed brains are byte-identical to before — and otherwise falls through to the configured chat_model. It stays a pure config read with no probe: readiness is the doctor's job, not something to put in the path of every unconfigured call. Two cases deliberately keep the Anthropic default: no chat_model at all, and a chat_model that is itself Anthropic. Someone who has configured nothing has an unfinished setup, not a local brain, and the truthful missing-key error beats a connection-refused from an invented default. The result still passes through enforceSubagentCapable, so a chat_model pointing at a tool-less model cannot become the subagent driver just because it arrived via this path. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two independent blockers made a keyless brain unable to run subagent work, even though the provider-agnostic gateway tool loop has shipped since v0.38 and supports these providers. The Anthropic SDK client was constructed at worker-REGISTRATION time. The SDK constructor throws on a missing key, so `gbrain jobs work` died during startup on a brain with no Anthropic credential — before any routing decision was reached, and regardless of which models its jobs targeted. It is now built lazily on first legacy-path use, and must stay that way. The handler then refused any non-Anthropic model unless agent.use_gateway_loop was set, pointing at a config key most users never find. That made a working loop look like an unsupported configuration. A non-Anthropic model now auto-routes to the gateway loop, because the legacy path calls the Anthropic Messages API directly and could never have run it. The flag keeps its real meaning: it opts Anthropic models into the gateway loop too, which is the actual rollout decision. Safety is unchanged. classifyCapabilities() at handler entry already rejects tool-less models and unknown providers, so auto-routing can only reach a provider the loop can drive. A once-per-model stderr notice records which loop ran, since the two differ in cost and cache behavior. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three user-facing surfaces still asserted the pre-fix contract. The init picker note said subagent features "require ANTHROPIC_API_KEY regardless of which chat model you pick" — shown at the exact moment a user deliberately chose a non-Anthropic provider, so a stale claim there tells them their choice does not count. It now states what actually differs on another provider: tool-calling quality varies by model, and without Anthropic's cache markers each turn re-sends the conversation. The doctor's subagent_capability check warned whenever chat_model was non-Anthropic without a key. That is now a supported configuration. In its place the check probes local endpoints directly, which catches the failure no key check can — a brain pointed at Ollama with the daemon stopped. The probe passes the config-resolved base URL so it tests the endpoint live traffic uses, and is bounded at 2s so a hung daemon cannot stall doctor. think's "no LLM available" text named ANTHROPIC_API_KEY as the fix. It now names the chat_model config key first. The NO_ANTHROPIC_API_KEY warning code is unchanged — it is a stable machine-readable signal with existing consumers; only the human-facing prose moved. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Seventeen tests covering the single user-visible claim — a brain configured for a local model runs with no hosted key — plus each of the four places that claim was independently false. Serial because the tier-default tests need both a temp GBRAIN_HOME holding a written config and a cleared ANTHROPIC_API_KEY; a developer machine with a real key in ~/.gbrain/config.json makes the no-key path untestable otherwise, the hazard test/helpers/no-anthropic-key.ts documents. The Anthropic-key-present case is pinned as byte-identical to the previous behavior, so the fallback cannot silently start firing for keyed brains. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
New docs/guides/local-models.md is the deliverable a user needs: the three-command setup, why the fix was four separate changes (each a place the behavior could regress), and an honest account of what local costs — prompt caching, the 4096-token default context that silently truncates retrieved context, tool-calling quality on small models, and embedding quality versus hosted. Covers remote Ollama and Ollama Cloud, including the trap that -cloud and :cloud model ids run on Ollama's servers even when the base URL points at the local daemon. The model id is the only thing that tells you, and it is a privacy decision rather than a performance one. Also documents the two-process llama.cpp setup (one model per server, --jinja required for tool calling) and why three of the four new hosted providers report cost as unknown. KEY_FILES entries for model-config.ts, subagent.ts and the openrouter recipe updated to current state — the openrouter entry claimed isAnthropicProvider "hard-pins gbrain's subagent infra to Anthropic-direct", which is no longer the operative gate. CHANGELOG entry under Unreleased; llms bundles regenerated for the CLAUDE.md reference-map row. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The no-prompt-caching notice said the loop "will run hot (cost scales linearly with conversation length)" and suggested switching to Anthropic for lower cost. On Ollama or llama-server the marginal token cost is zero, so the advice is wrong in both halves — and it is the message a deliberately-local brain sees on every process. Free providers now get the accurate version: the loop re-sends the conversation each turn, so it gets slower, and the fix is a larger context window rather than a different provider. Detected from the recipe's declared chat pricing rather than an id allowlist, so a future local recipe is covered without editing a list. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bedrock (SigV4-signed regional hosts), Copilot (OAuth device flow, no static key) and poolside (enterprise-gated, contract unverifiable against public docs) each need transport work in the gateway's implementation switch rather than a recipe, which is pure data over an OpenAI-compatible endpoint. All three are reachable through the existing litellm proxy recipe today. Recording the omission explicitly so the gap is a known decision rather than something a reader has to infer from an absence. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…und-trip guard `gbrain agent --help` still said non-Anthropic providers "need agent.use_gateway_loop enabled" and that the worker runs the legacy Anthropic-direct path by default. Neither is true now — a non-Anthropic model auto-routes to the gateway loop, and local recipes need no credential at all. The flag's real remaining purpose (routing ANTHROPIC models through the gateway loop) is stated instead. test/config-set.test.ts sourced the garrytan#2753 command string from the doctor warning this branch removed. The invariant it guards is unchanged and still worth keeping — the exact command gbrain prints must be accepted by `config set` without --force — so it now extracts that command from `gbrain agent --help`, which is the surface that still prints it. Same drift protection, anchored to a live surface rather than a deleted one. Adds the inverse assertion the removal earns: doctor must NOT warn about a non-Anthropic chat_model with no key, since warning would tell users to fix a configuration that is already correct. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A keyless brain ran all four tiers on one model. That wastes a local fleet in both directions: a 3B model is the right utility-tier classifier and the wrong deep-tier reasoner. `gbrain models autotune` reads what the user has actually pulled and assigns each tier, printing its reasoning and the context it measured. Discovery runs ONCE and writes `models.tier.*`. Resolution stays a pure config read — a network round-trip in front of every unconfigured LLM call would be a worse regression than the flat defaults it fixes. Wired into `gbrain init` for Ollama chat models so a fresh local brain is tiered without a second command, fail-open so an unreachable daemon at install time (the normal case — people configure before starting `ollama serve`) degrades to the previous behavior instead of aborting init. The `completion AND tools` predicate is load-bearing, not defensive. Several Ollama EMBEDDING models advertise `tools` without `completion`; `qwen3-embedding:8b` reports `[tools,embedding]` and at 7.6B outranks most genuine chat models by size, so selecting on `tools` alone puts a model that cannot generate text into a reasoning tier. Every rejected model is printed with its reason, so the pick is auditable rather than magic. Ranking is by on-disk BYTES, not parameter count: quantized and MLX builds frequently report no parameter count, and a parameter sort silently drops them to the bottom. Two tier rules are deliberate — utility prefers the smallest model WITHOUT `thinking` (classification returns a label; reasoning tokens are overhead), and reasoning takes the runner-up so deep-tier latency is not paid on every ordinary call. Size is a proxy for capability, not a quality ranking, which is why each assignment prints its justification and stays overridable. Never overwrites a hand-set tier without --force, so re-running after a pull is safe. Ollama only, deliberately: llama-server serves one model chosen at launch and LiteLLM proxies opaque backends with no capability API. Both keep the single-model behavior. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.